package com.snail.monitor;

import com.sun.jna.examples.win32.Kernel32;
import com.sun.jna.examples.win32.User32;
import com.sun.jna.examples.win32.User32.HHOOK;
import com.sun.jna.examples.win32.User32.KBDLLHOOKSTRUCT;
import com.sun.jna.examples.win32.User32.LowLevelKeyboardProc;
import com.sun.jna.examples.win32.User32.MSG;
import com.sun.jna.examples.win32.W32API.HMODULE;
import com.sun.jna.examples.win32.W32API.LRESULT;
import com.sun.jna.examples.win32.W32API.WPARAM;
import com.sun.jna.platform.win32.WinUser;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

public class KeyboardMonitor extends Thread {

    private static HHOOK hHook;
    private static LowLevelKeyboardProc keyboardHook;
    final static User32 user32 = User32.INSTANCE;
    private boolean running = true;
    private static List keyDowns = new ArrayList();

    private MonitorRecord record;

    public KeyboardMonitor(boolean running, MonitorRecord record) {

        setRunning(running);
        setRecord(record);
    }

    private void keyDown(int keyCode) {

        if (keyDowns.contains(keyCode + "")) {
            return;
        }
        keyDowns.add(keyCode + "");
        record.keyDown(keyCode);
    }

    private void keyUp(int keyCode) {

        keyDowns.remove(keyCode + "");
        record.keyUp(keyCode);
    }

    public void run() {

        HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
        keyboardHook = new LowLevelKeyboardProc() {

            public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info) {

                if (!isRunning()) {
                    return null;
                }
                if (nCode >= 0) {
                    switch (wParam.intValue()) {
                        case WinUser.WM_KEYUP:
                            keyUp(getKyeEventKeyCode(info.vkCode));
                            break;
                        case WinUser.WM_KEYDOWN:
                            keyDown(getKyeEventKeyCode(info.vkCode));
                            break;
                        case WinUser.WM_SYSKEYDOWN:
                            keyDown(getKyeEventKeyCode(info.vkCode));
                            break;
                        case WinUser.WM_SYSKEYUP:
                            keyDown(getKyeEventKeyCode(info.vkCode));
                            break;
                    }
                }
                return user32.CallNextHookEx(hHook, nCode, wParam, info.getPointer());
            }
        };
        hHook = user32.SetWindowsHookEx(User32.WH_KEYBOARD_LL, keyboardHook, hMod, 0);
        int result;
        MSG msg = new MSG();
        while ((result = user32.GetMessage(msg, null, 0, 0)) != 0) {
            if (result == -1) {
                break;
            } else {
                user32.TranslateMessage(msg);
                user32.DispatchMessage(msg);
            }
        }
        user32.UnhookWindowsHookEx(hHook);
    }

    private int getKyeEventKeyCode(int vkCode) {

        switch (vkCode) {
            case 13:
                return KeyEvent.VK_ENTER;
            case 44:
                return KeyEvent.VK_PRINTSCREEN;
            case 45:
                return KeyEvent.VK_DELETE;
            case 46:
                return KeyEvent.VK_INSERT;
            case 91:
            case 92:
                return KeyEvent.VK_WINDOWS;
            case 93:
                return KeyEvent.VK_CONTEXT_MENU;
            case User32.VK_LSHIFT:
            case User32.VK_RSHIFT:
                return KeyEvent.VK_SHIFT;
            case User32.VK_LCONTROL:
            case User32.VK_RCONTROL:
                return KeyEvent.VK_CONTROL;
            case User32.VK_LMENU:
            case User32.VK_RMENU:
                return KeyEvent.VK_ALT;
            case 186:
                return KeyEvent.VK_SEMICOLON;
            case 187:
                return KeyEvent.VK_EQUALS;
            case 188:
                return KeyEvent.VK_COMMA;
            case 189:
                return KeyEvent.VK_MINUS;
            case 190:
                return KeyEvent.VK_PERIOD;
            case 191:
                return KeyEvent.VK_SLASH;
            case 219:
                // [
                return KeyEvent.VK_OPEN_BRACKET;
            case 221:
                // ]
                return KeyEvent.VK_CLOSE_BRACKET;
            case 220:
                return KeyEvent.VK_BACK_SLASH;
            case 255:
                return KeyEvent.VK_FULL_WIDTH;
            default:
                return vkCode;
        }
    }

    public boolean isRunning() {

        return running;
    }

    public void setRunning(boolean running) {

        this.running = running;
    }

    public MonitorRecord getRecord() {

        return record;
    }

    public void setRecord(MonitorRecord record) {

        this.record = record;
    }
}
